/**
 *   _                                                          _   
 *  | | ____ _ _ __ _ __ ___   __ _  ___           _ __ ___ ___| |_ 
 *  | |/ / _` | '__| '_ ` _ \ / _` |/ __|  _____  | '__/ _ | __| __|
 *  |   < (_| | |  | | | | | | (_| | (__  |_____| | | |  __|__ \ |_ 
 *  |_|\_\__,_|_|  |_| |_| |_|\__,_|\___|         |_|  \___|___/\__|
 *         
 *  Copyright  2011 Kondra, Inc. 
 * 
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at:
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package com.kondra.karmac.rest
{
   import flash.events.Event;
   import flash.events.IOErrorEvent;
   import flash.events.TimerEvent;
   import flash.utils.Timer;
   import flash.utils.getTimer;
   
   /**
    * Abstract implementation of the <code>IServiceTask</code> interface. This class
    * provides the basic implementation required for invoking services, and should
    * be subclassed for specific request implementations.
    * 
    * @author Jared Kaplan
    */ 
   public class AbstractServiceTask implements IServiceTask
   {
      private var m_id:int;
      private var m_serviceEndpoint:IServiceEndpoint;
      private var m_description:String;
      
      private var m_retry:Boolean;
      private var m_retryCount:int = 0;
      
      private var m_startTime:int;
      private var m_completeTime:int;
      private var m_timeout:int;
      private var m_timer:Timer;
      
      // task id incrementer
      static private var s_nextTaskId:int = 1;
      
      // notification lists
      private var m_notificationCompleteList:Array;
      private var m_notificationErrorList:Array;
      private var m_notificationProgressList:Array;
      
      public function AbstractServiceTask( serviceEndpoint:IServiceEndpoint = null ) {
         m_id = s_nextTaskId++;
         m_serviceEndpoint = serviceEndpoint;
         m_description = description;
      }
      
      /**
       * Called to initialize the service task before it is invoked.
       */  
      protected function handleInitializeTask() : void {}
      
      /**
       * Called when the service request has been cancelled.
       */ 
      protected function handleCancelTask() : void {}
      
      /**
       * Called when the service request should be started.
       */ 
      protected function handleInvokeTask() : void {}
      
      /**
       * Called to invoke the service request. The actual service gets invoked
       * by the subclass in <code>handleInvokeTask()</code>, but this method
       * sets up the timeout behavior before making the request.
       * 
       * @see handleInvokeTask
       */ 
      private function invoke() : void
      {
         // if a timeout has been specified then setup the timer object
         // to handle the timeout event
         if( m_timeout > 0 )
         {
            if( m_timer == null )
            {
               m_timer = new Timer( m_timeout );
               m_timer.addEventListener( TimerEvent.TIMER_COMPLETE, handleRequestTimeout );
            }
            else
               m_timer.reset();
         }
         
         // when a service is invoked the retry flag gets reset
         m_retry = false;
         
         // let the subclass provide the service request implementation
         handleInvokeTask();
      }
      
      /**
       * Called when a retry attempt is being made. This methods increments
       * the retry count and calls <code>invoke()</code>.
       */ 
      private function attemptRetry() : void {
         m_retryCount++;
         invoke();
      }
      
      /**
       * Returns <code>true</code> if a complete notification handler has been set
       * for this service task.
       */ 
      protected function isCompleteHandlerSet() : Boolean { 
         return m_notificationCompleteList != null &&  m_notificationCompleteList.length > 0; 
      }
      
      /**
       * Returns <code>true</code> if a complete notification handler has been set
       * for this service task.
       */ 
      protected function isErrorHandlerSet() : Boolean { 
         return m_notificationErrorList != null &&  m_notificationErrorList.length > 0; 
      }
      
      /**
       * Returns <code>true</code> if a progress notification handler has been set
       * for this service task.
       */ 
      protected function isProgressHandlerSet() : Boolean { 
         return m_notificationProgressList != null &&  m_notificationProgressList.length > 0; 
      }
      
      /**
       * Called by the subclass service implementation when the service request is complete.
       * This method calls all of the service notification handlers that have been
       * registered with this serrvice task.
       * 
       * @param data          Data object that is associated with this service request
       * @param source        Source object that made the request (will usually be the
       *                      service task object itself)
       * @param event         Event that is associated with the notification
       */ 
      protected function handleRequestComplete( data:Object = null, source:Object = null, event:Event = null ) : void 
      {
         var result:ServiceResult = createResult( data, source, event );
         
         // mark completion time
         m_completeTime = getTimer();
         
         // send message to all notification listeners
         for each( var next:ServiceNotificationHandler in m_notificationCompleteList )
            next.notifyComplete( result );
         
         // user may have specified a retry attemp if a server error was sent
         // from the service...
         if( m_retry )
            attemptRetry();
      }
      
      /**
       * Called by the subclass service implementation when an error has occured while 
       * making the service request. This method calls all of the service notification
       * handlers that have been registered with this service task.
       * 
       * @param data          Data object that is associated with this service request
       * @param source        Source object that made the request (will usually be the
       *                      service task object itself)
       * @param event         Event that is associated with the notification
       * @param error         Array of error messages that occurred while executing the
       *                      service task
       */ 
      protected function handleRequestError( data:Object = null, source:Object = null, event:Event = null, errors:Array = null ) : void 
      {
         var result:ServiceResult = createResult( data, source, event, errors );
         
         // mark completion time
         m_completeTime = getTimer();
         
         // send message to all notification listeners
         for each( var next:ServiceNotificationHandler in m_notificationErrorList )
            next.notifyError( result );
         
         // if user has flaged the task for retry attempt, invoke the service again...
         if( m_retry )
            attemptRetry();
      }
      
      /**
       * Called by the subclass service implementation while the service request 
       * is being made to report progress. This method calls all of the service notification
       * handlers that have been registered with this service task.
       * 
       * @param data          Data object that is associated with this service request
       * @param source        Source object that made the request (will usually be the
       *                      service task object itself)
       * @param event         Event that is associated with the notification
       */ 
      protected function handleRequestProgress( data:Object = null, source:Object = null, event:Event = null ) : void 
      {
         var result:ServiceResult = createResult( data, source, event );
         
         // send message to all notification listeners
         for each( var next:ServiceNotificationHandler in m_notificationProgressList )
            next.notifyProgress( result );
      }
      
      /**
       * Called when the task has taken longer to execute than the specified timeout
       * duration. This method cancels the current task and calls the error notification
       * handlers with a network error message.
       * 
       * @param               Timer event associated with the service timeout
       */
      private function handleRequestTimeout( event:TimerEvent ) : void {
         cancel();
         handleRequestError( new IOErrorEvent( IOErrorEvent.NETWORK_ERROR, false, false, "AbstractServiceTask: request timeout!" ) );
      }
      
      /**
       * Private method to create the <code>ServiceResult</code> object that is passed
       * to the notification handlers.
       * 
       * @param data          Data object that is associated with this service request
       * @param source        Source object that made the request (will usually be the
       *                      service task object itself)
       * @param event         Event that is associated with the notification
       * @param error         Array of error messages that occurred while executing the
       *                      service task
       */ 
      private function createResult( data:Object = null, source:Object = null, event:Event = null, errors:Array = null ) : ServiceResult 
      {
         var factory:IServiceDataFactory = serviceEndpoint != null ? serviceEndpoint.getServiceDataFactory() : null;
         var result:ServiceResult = null;
         
         if( factory != null )
            result = factory.createServiceResult( serviceEndpoint.getResultFormat(), data, source, event, errors );
         else
            result = createDefaultServiceResult( data, source, event, errors );
         
         return result;
	  }
	  
	  protected function createDefaultServiceResult( data:Object = null, source:Object = null, event:Event = null, errors:Array = null ) : ServiceResult {
		  return new ServiceResult( data, source, event, errors );
	  }
      
      //
      // IServiceTask implementation
      //
      
      /**
       * @inheritDoc
       */ 
      public final function start() : void 
      {
         if( m_startTime == 0 )
         {
            m_startTime = getTimer();
            m_retryCount = 0;
            m_retry = false;
            
            // initialize the task before we invoke it
            handleInitializeTask();
            
            // invoke the task
            invoke();
         }
         else
         {
            throw new Error( "AbstractServiceTask:start() : Service task has already been started!" );
         }
      }
      
      /**
       * @inheritDoc
       */ 
      public final function cancel() : void 
      {
         m_completeTime = getTimer();
         handleCancelTask();
      }
      
      /**
       * @inheritDoc
       */ 
      public final function retry() : void {
         m_retry = true;
      }
      
      /**
       * @inheritDoc
       */ 
      public function getRetryCount() : int {
         return m_retryCount;
      }
      
      /**
       * @inheritDoc
       */ 
      public function isComplete() : Boolean {
         return false;
      }
      
      /**
       * @inheritDoc
       */ 
      public function addNotificationHandler( handler:ServiceNotificationHandler ) : void
      {
         if( handler.hasNotificationComplete() )
         {
            if( m_notificationCompleteList == null )
               m_notificationCompleteList = new Array();
            
            m_notificationCompleteList.push( handler );
         }
         
         if( handler.hasNotificationError() )
         {
            if( m_notificationErrorList == null ) 
               m_notificationErrorList = new Array();
            
            m_notificationErrorList.push( handler );
         }
         
         if( handler.hasNotificationProgress() )
         {
            if( m_notificationProgressList == null ) 
               m_notificationProgressList = new Array();
            
            m_notificationProgressList.push( handler );
         }
      }
      
      /**
       * @inheritDoc
       */ 
      public function getId() : int { 
         return m_id; 
      }
      
      /**
       * @inheritDoc
       */ 
      public function getTime() : int { 
         return isComplete() ? m_completeTime - m_startTime : getTimer() - m_startTime; 
      }
      
      /**
       * @inheritDoc
       */ 
      public function getSize() : int { 
         return 0; 
      }
      
      /**
       * @inheritDoc
       */ 
      public function getUrl() : String  { 
         return null; 
      }

      //
      // getter/setter methods
      //
      public function get serviceEndpoint() : IServiceEndpoint { return m_serviceEndpoint; }
      public function get description() : String { return m_description; }
      
      public function get timeout() : int { return m_timeout; }
      public function set timeout( timeout:int ) : void { m_timeout = timeout; }
   }
}